import sys
import logging
import requests
import socket
import threading
import queue
import concurrent.futures
import json
import os
import time
import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
from urllib.parse import urljoin, urlparse
import re
from pathlib import Path
from ftplib import FTP
import paramiko
import pymysql
import pymongo
from pymssql import connect
import telnetlib
import requests.packages.urllib3
from concurrent.futures import ThreadPoolExecutor
requests.packages.urllib3.disable_warnings()

# 配置日志
logging.basicConfig(
    filename='error.log',
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

try:
    from playwright.sync_api import sync_playwright
except Exception as e:
    logging.error(f"Failed to import playwright: {e}")
    logging.error(f"Python path: {sys.path}")
    sys.exit(1)

BASE_DIR = Path(__file__).resolve().parent
LOG_DIR = BASE_DIR / "logs"
SCREENSHOT_DIR = BASE_DIR / "screen_shot"
LOG_DIR.mkdir(exist_ok=True)
SCREENSHOT_DIR.mkdir(exist_ok=True)
logging.basicConfig(
    level=logging.DEBUG,
    format="%(levelname)s - %(asctime)s - %(name)s - %(message)s",
    handlers=[
        logging.FileHandler(
            LOG_DIR / time.strftime("main.log"),
            encoding='utf8',
        )
    ])

# 敏感关键词列表
keywords = ["电视剧","🧧欢迎","竞猜","天博","体育","日本","电影","香港","澳门","电竞","竞技","Sports","棋牌",'北京赛车',"港澳",
            'BTi体育', '多彩奇利', '永利国际', '新浪彩票', '亚博', '万博', '北京pk', '竞彩', '博彩', '购彩',
            '彩票', '网赚', '投注', '开奖', '好彩', '大乐透', '皇冠娱乐', '赛车app', '计划app', '分赛车',
            '亚洲集团', '亚洲杯', '欧洲杯', '老虎机', '水果机', '百家乐', '维加斯', '威尼斯人', '真人娱乐', '真人游戏',
            '真人现金', '真人在线', '在线开户', '现金棋牌', '亿濠扑克', '银河国际', '国际娱乐', '娱乐国际', '国际官网',
            '国际客服', '在线娱乐', '快3', '快三', '分分彩', '时时彩', '龙虎', '澳门银河', '新澳门', '夜场', '同城交友']

class WeakPasswordScanner:
    def __init__(self):
        # 定义常见服务的默认端口
        self.service_ports = {
            'ftp': 21,
            'ssh': 22,
            'telnet': 23,
            'mssql': 1433,
            'mysql': 3306,
            'postgresql': 5432,
            'mongodb': 27017,
            'phpmyadmin': 80,
            'tomcat': 8080,
            'weblogic': 7001,
            'smb': 445  # IPC$
        }
        
        # 默认用户名列表
        self.default_users = {
            'ftp': ['anonymous', 'admin', 'ftp'],
            'ssh': ['root', 'admin'],
            'telnet': ['root', 'admin'],
            'mssql': ['sa'],
            'mysql': ['root', 'admin'],
            'postgresql': ['postgres', 'admin'],
            'mongodb': ['admin', 'root'],
            'phpmyadmin': ['root', 'admin'],
            'tomcat': ['tomcat', 'admin'],
            'weblogic': ['weblogic', 'admin'],
            'smb': ['administrator', 'guest']
        }
        
        # 默认密码列表
        self.default_passwords = [
            '', 'admin', 'password', '123456', 'root',
            'admin123', 'Admin123', 'admin@123',
            'P@ssw0rd', 'password123', 'test123',
            '12345678', 'qwerty', '111111', '123123'
        ]

    def check_ftp(self, host, port, username, password, timeout):
        try:
            with FTP() as ftp:
                ftp.connect(host, port, timeout=timeout)
                ftp.login(username, password)
                return True
        except:
            return False

    def check_ssh(self, host, port, username, password, timeout):
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(host, port, username, password, timeout=timeout)
            ssh.close()
            return True
        except:
            return False

    def check_telnet(self, host, port, username, password, timeout):
        try:
            tn = telnetlib.Telnet(host, port, timeout=timeout)
            tn.read_until(b"login: ")
            tn.write(username.encode('ascii') + b"\n")
            tn.read_until(b"Password: ")
            tn.write(password.encode('ascii') + b"\n")
            result = tn.read_until(b"$ ", timeout=5)
            tn.close()
            return b'Login incorrect' not in result
        except:
            return False

    def check_mysql(self, host, port, username, password, timeout):
        try:
            conn = pymysql.connect(
                host=host, port=port,
                user=username, password=password,
                connect_timeout=timeout
            )
            conn.close()
            return True
        except:
            return False

    def check_mssql(self, host, port, username, password, timeout):
        try:
            conn = connect(
                server=host, port=port,
                user=username, password=password,
                login_timeout=timeout
            )
            conn.close()
            return True
        except:
            return False

    def check_mongodb(self, host, port, username, password, timeout):
        try:
            client = pymongo.MongoClient(
                host=host, port=port,
                username=username, password=password,
                serverSelectionTimeoutMS=timeout*1000
            )
            client.server_info()
            client.close()
            return True
        except:
            return False

    def check_postgresql(self, host, port, username, password, timeout):
        try:
            import psycopg2
            conn = psycopg2.connect(
                host=host, port=port,
                user=username, password=password,
                connect_timeout=timeout
            )
            conn.close()
            return True
        except:
            return False

class DetectionGUI:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("Web检测工具")
        self.window.geometry("800x600")
        
        # 创建主框架
        main_frame = ttk.Frame(self.window, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # URL输入区域
        url_frame = ttk.LabelFrame(main_frame, text="URL输入", padding="5")
        url_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E))
        
        self.url_text = scrolledtext.ScrolledText(url_frame, height=10)
        self.url_text.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E))
        
        # 按钮区域
        button_frame = ttk.Frame(main_frame, padding="5")
        button_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E))
        
        ttk.Button(button_frame, text="上传文件", command=self.upload_file).grid(row=0, column=0, padx=5)
        ttk.Button(button_frame, text="开始检测", command=self.safe_start_detection).grid(row=0, column=1, padx=5)
        ttk.Button(button_frame, text="CURL请求", command=self.curl_request).grid(row=0, column=2, padx=5)
        ttk.Button(button_frame, text="端口扫描", command=self.show_port_scan_dialog).grid(row=0, column=3, padx=5)
        ttk.Button(button_frame, text="目录扫描", command=self.show_dir_scan_dialog).grid(row=0, column=4, padx=5)
        ttk.Button(button_frame, text="弱口令检测", command=self.show_weak_password_dialog).grid(row=0, column=5, padx=5)
        
        # 进度显示区域
        progress_frame = ttk.LabelFrame(main_frame, text="检测进度", padding="5")
        progress_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        self.progress_text = scrolledtext.ScrolledText(progress_frame, height=15)
        self.progress_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        self.progress_text.configure(state='disabled')

        # 定义敏感关键词列表
        self.sensitive_keywords = [
            'password', 'admin', 'root', 'mysql', 'config',
            'database', 'secret', 'key', 'token', 'api',
            'username', 'passwd', 'pwd', 'auth', 'administrator',
            'phpinfo', 'sys', 'system', 'backdoor', 'shell'
        ]

    def setup_logging(self):
        """配置日志输出到GUI"""
        class TextHandler(logging.Handler):
            def __init__(self, text_widget):
                logging.Handler.__init__(self)
                self.text_widget = text_widget

            def emit(self, record):
                msg = self.format(record)
                def append():
                    self.text_widget.configure(state='normal')
                    self.text_widget.insert(tk.END, msg + '\n')
                    self.text_widget.configure(state='disabled')
                    self.text_widget.see(tk.END)
                self.text_widget.after(0, append)

        text_handler = TextHandler(self.progress_text)
        logging.getLogger().addHandler(text_handler)
        text_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))

    def upload_file(self):
        """处理文件上传"""
        file_path = filedialog.askopenfilename(
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    content = file.read()
                    self.url_text.delete('1.0', tk.END)
                    self.url_text.insert('1.0', content)
            except Exception as e:
                messagebox.showerror("错误", f"读取文件时出错: {str(e)}")

    def get_urls(self):
        """获取输入的URL列表，自动理http和https"""
        urls = self.url_text.get('1.0', tk.END).strip()
        result = []
        for url in urls.split('\n'):
            url = url.strip()
            if not url:
                continue
            
            if url.startswith(('http://', 'https://')):
                # 已经有协议前缀的URL直接添加
                result.append(url)
            else:
                # 没有协议前缀的URL添加http和https两个版本
                result.append(f'http://{url}')
                result.append(f'https://{url}')
            
        return result

    def safe_start_detection(self):
        """安全的开始检测方法"""
        try:
            self.start_detection()
        except Exception as e:
            messagebox.showerror("错误", f"检测过程出错：{str(e)}")
            logging.error(f"Detection error: {str(e)}")

    def start_detection(self):
        """开始检测"""
        urls = self.get_urls()
        if not urls:
            messagebox.showwarning("警告", "请输入至少一个URL")
            return

        # 清空进度显示
        self.progress_text.configure(state='normal')
        self.progress_text.delete('1.0', tk.END)
        self.progress_text.configure(state='disabled')

        def check_url(url):
            try:
                if not url.startswith(('http://', 'https://')):
                    url = 'http://' + url

                response = requests.get(url, timeout=10, verify=False)
                self.analyze_results(url, response.text, response.status_code)
                
            except Exception as e:
                error_msg = f"检测 {url} 时出错: {str(e)}\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, error_msg)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')
                logging.error(f"Detection failed for {url}: {str(e)}")

        # 在新线程中运行检测
        threading.Thread(target=lambda: [check_url(url) for url in urls], 
                       daemon=True).start()

    def analyze_results(self, url, content, status_code):
        """分析检测结果"""
        try:
            results = []
            
            # 检查状态码
            if status_code != 200:
                results.append(f"警告：页面返回状态码 {status_code}")
                
            # 检查敏感信息
            for keyword in self.sensitive_keywords:
                if keyword.lower() in content.lower():
                    results.append(f"发现敏感关键词：{keyword}")
                    
            # 检查特殊文件和目录
            special_paths = [
                'admin', 'login', 'wp-admin', 'administrator',
                'phpinfo.php', 'test.php', 'backup', '.git',
                'config', '.env', 'database', '.htaccess'
            ]
            
            for path in special_paths:
                try:
                    test_url = urljoin(url, path)
                    response = requests.get(test_url, timeout=5, verify=False)
                    if response.status_code in [200, 301, 302]:
                        results.append(f"发现敏感路径：{test_url}")
                except:
                    continue
                    
            # 检查常见漏洞特征
            vulnerability_patterns = [
                'sql error', 'mysql error', 'error in your sql syntax',
                'warning: mysql', 'uncaught exception', 'include_path',
                'undefined index', 'php warning', 'debug'
            ]
            
            for pattern in vulnerability_patterns:
                if pattern.lower() in content.lower():
                    results.append(f"发现潜在漏洞特征：{pattern}")
                    
            # 如果没有发现问题
            if not results:
                results.append("未发现明显安全问题")
                
            # 更新界面显示结果
            result_text = f"\n检测URL: {url}\n"
            result_text += "=" * 50 + "\n"
            result_text += "\n".join(results) + "\n"
            result_text += "-" * 50 + "\n"
            
            self.progress_text.configure(state='normal')
            self.progress_text.insert(tk.END, result_text)
            self.progress_text.see(tk.END)
            self.progress_text.configure(state='disabled')
            
            # 记录到日志
            logging.info(f"Analysis completed for {url}")
            
        except Exception as e:
            error_msg = f"分析 {url} 时出错: {str(e)}\n"
            self.progress_text.configure(state='normal')
            self.progress_text.insert(tk.END, error_msg)
            self.progress_text.see(tk.END)
            self.progress_text.configure(state='disabled')
            logging.error(f"Analysis failed for {url}: {str(e)}")

    def run(self):
        """运行GUI"""
        self.window.mainloop()

    def curl_request(self):
        """执行CURL请求"""
        urls = self.get_urls()
        if not urls:
            messagebox.showwarning("警告", "请输入至少一个URL")
            return

        # 清空进度显示
        self.progress_text.configure(state='normal')
        self.progress_text.delete('1.0', tk.END)
        self.progress_text.configure(state='disabled')

        def run_curl():
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
            }
            
            for url in urls:
                try:
                    response = requests.get(url, headers=headers, timeout=10, verify=False)
                    status_code = response.status_code
                    content_type = response.headers.get('Content-Type', 'Unknown')
                    server = response.headers.get('Server', 'Unknown')
                    
                    result = f"URL: {url}\n"
                    result += f"状态码: {status_code}\n"
                    result += f"内容类型: {content_type}\n"
                    result += f"服务器: {server}\n"
                    result += f"响应头:\n{json.dumps(dict(response.headers), indent=2, ensure_ascii=False)}\n"
                    result += "-" * 50 + "\n"
                    
                    self.progress_text.configure(state='normal')
                    self.progress_text.insert(tk.END, result)
                    self.progress_text.see(tk.END)
                    self.progress_text.configure(state='disabled')
                    
                except Exception as e:
                    error_msg = f"请求 {url} 失败: {str(e)}\n" + "-" * 50 + "\n"
                    self.progress_text.configure(state='normal')
                    self.progress_text.insert(tk.END, error_msg)
                    self.progress_text.see(tk.END)
                    self.progress_text.configure(state='disabled')
                    logging.error(f"CURL request failed for {url}: {str(e)}")

        # 在新线程中运行CURL请求
        threading.Thread(target=run_curl, daemon=True).start()

    def show_port_scan_dialog(self):
        """显示端口扫描对话框"""
        dialog = tk.Toplevel(self.window)
        dialog.title("端口扫描设置")
        dialog.geometry("400x300")
        dialog.transient(self.window)
        dialog.grab_set()

        # 创建选项框架
        options_frame = ttk.LabelFrame(dialog, text="扫描选项", padding="5")
        options_frame.pack(fill=tk.X, padx=5, pady=5)

        # 扫描模式选择
        self.scan_mode = tk.StringVar(value="quick")
        ttk.Radiobutton(options_frame, text="快速扫描（常用端口）", 
                       variable=self.scan_mode, value="quick").pack(anchor=tk.W)
        ttk.Radiobutton(options_frame, text="自定义端口范围", 
                       variable=self.scan_mode, value="custom").pack(anchor=tk.W)

        # 自定义端口范围输入
        port_frame = ttk.Frame(options_frame)
        port_frame.pack(fill=tk.X, pady=5)
        ttk.Label(port_frame, text="起始端口:").pack(side=tk.LEFT)
        self.start_port = ttk.Entry(port_frame, width=10)
        self.start_port.pack(side=tk.LEFT, padx=5)
        self.start_port.insert(0, "1")
        
        ttk.Label(port_frame, text="结束端口:").pack(side=tk.LEFT)
        self.end_port = ttk.Entry(port_frame, width=10)
        self.end_port.pack(side=tk.LEFT, padx=5)
        self.end_port.insert(0, "1024")

        # 超时设置
        timeout_frame = ttk.Frame(options_frame)
        timeout_frame.pack(fill=tk.X, pady=5)
        ttk.Label(timeout_frame, text="超时时间(秒):").pack(side=tk.LEFT)
        self.timeout = ttk.Entry(timeout_frame, width=10)
        self.timeout.pack(side=tk.LEFT, padx=5)
        self.timeout.insert(0, "1")

        # 线程数设置
        thread_frame = ttk.Frame(options_frame)
        thread_frame.pack(fill=tk.X, pady=5)
        ttk.Label(thread_frame, text="线程数:").pack(side=tk.LEFT)
        self.thread_count = ttk.Entry(thread_frame, width=10)
        self.thread_count.pack(side=tk.LEFT, padx=5)
        self.thread_count.insert(0, "50")

        # 开始扫描按钮
        ttk.Button(dialog, text="开始扫描", 
                  command=lambda: self.start_port_scan(dialog)).pack(pady=10)

    def start_port_scan(self, dialog):
        """开始端口扫描"""
        urls = self.get_urls()
        if not urls:
            messagebox.showwarning("警告", "请输入至少一个URL")
            return

        try:
            timeout = float(self.timeout.get())
            thread_count = int(self.thread_count.get())
            if self.scan_mode.get() == "custom":
                start_port = int(self.start_port.get())
                end_port = int(self.end_port.get())
                if not (1 <= start_port <= 65535 and 1 <= end_port <= 65535):
                    raise ValueError("端口范围必须在1-65535之间")
                if start_port > end_port:
                    raise ValueError("起始端口不能大于结束端口")
            dialog.destroy()
        except ValueError as e:
            messagebox.showerror("错误", str(e))
            return

        # 清空进度显示
        self.progress_text.configure(state='normal')
        self.progress_text.delete('1.0', tk.END)
        self.progress_text.configure(state='disabled')

        def scan_port(host, port):
            """扫描单个端口"""
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(timeout)
                result = sock.connect_ex((host, port))
                sock.close()
                if result == 0:
                    return port
                return None
            except:
                return None

        def get_common_ports():
            """返回常用端口列表"""
            return [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 
                   465, 587, 993, 995, 1433, 1521, 3306, 3389, 5432, 5900, 
                   6379, 8080, 8443, 27017]

        def scan_host(url):
            """扫描单个主机"""
            try:
                # 从URL中提取主机名
                host = url.split('://')[1].split('/')[0].split(':')[0]
                ip = socket.gethostbyname(host)
                
                result = f"\n开始扫描主机: {host} ({ip})\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, result)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')

                # 确定要扫描的端口
                if self.scan_mode.get() == "quick":
                    ports = get_common_ports()
                else:
                    ports = range(start_port, end_port + 1)

                # 使用线程池扫描端口
                open_ports = []
                with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
                    future_to_port = {executor.submit(scan_port, ip, port): port for port in ports}
                    for future in concurrent.futures.as_completed(future_to_port):
                        port = future.result()
                        if port is not None:
                            open_ports.append(port)
                            try:
                                service = socket.getservbyport(port)
                            except:
                                service = "unknown"
                            result = f"发现开放端口 {port}/tcp - {service}\n"
                            self.progress_text.configure(state='normal')
                            self.progress_text.insert(tk.END, result)
                            self.progress_text.see(tk.END)
                            self.progress_text.configure(state='disabled')

                if not open_ports:
                    result = f"未发现开放端口\n"
                    self.progress_text.configure(state='normal')
                    self.progress_text.insert(tk.END, result)
                    self.progress_text.see(tk.END)
                    self.progress_text.configure(state='disabled')

                result = "-" * 50 + "\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, result)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')

            except Exception as e:
                error_msg = f"扫描 {url} 时出错: {str(e)}\n" + "-" * 50 + "\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, error_msg)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')
                logging.error(f"Port scan failed for {url}: {str(e)}")

        # 在新线程中运行端口扫描
        threading.Thread(target=lambda: [scan_host(url) for url in urls], 
                       daemon=True).start()

    def show_dir_scan_dialog(self):
        """显示目录扫描对话框"""
        dialog = tk.Toplevel(self.window)
        dialog.title("Web目录扫描")
        dialog.geometry("500x400")
        dialog.transient(self.window)
        dialog.grab_set()

        # 创建选项框架
        options_frame = ttk.LabelFrame(dialog, text="扫描选项", padding="5")
        options_frame.pack(fill=tk.X, padx=5, pady=5)

        # 扫描模式选择
        self.dir_scan_mode = tk.StringVar(value="quick")
        ttk.Radiobutton(options_frame, text="快速扫描（常见目录）", 
                       variable=self.dir_scan_mode, value="quick").pack(anchor=tk.W)
        ttk.Radiobutton(options_frame, text="自定义字典扫描", 
                       variable=self.dir_scan_mode, value="custom").pack(anchor=tk.W)

        # 自定义字典文件选择
        dict_frame = ttk.Frame(options_frame)
        dict_frame.pack(fill=tk.X, pady=5)
        self.dict_path = tk.StringVar()
        ttk.Label(dict_frame, text="字典文件:").pack(side=tk.LEFT)
        ttk.Entry(dict_frame, textvariable=self.dict_path, width=30).pack(side=tk.LEFT, padx=5)
        ttk.Button(dict_frame, text="浏览", 
                  command=self.select_dict_file).pack(side=tk.LEFT)

        # 扫描设置
        settings_frame = ttk.Frame(options_frame)
        settings_frame.pack(fill=tk.X, pady=5)

        # 线程数设置
        thread_frame = ttk.Frame(settings_frame)
        thread_frame.pack(fill=tk.X, pady=5)
        ttk.Label(thread_frame, text="线程数:").pack(side=tk.LEFT)
        self.dir_thread_count = ttk.Entry(thread_frame, width=10)
        self.dir_thread_count.pack(side=tk.LEFT, padx=5)
        self.dir_thread_count.insert(0, "10")

        # 超时设置
        timeout_frame = ttk.Frame(settings_frame)
        timeout_frame.pack(fill=tk.X, pady=5)
        ttk.Label(timeout_frame, text="超时时间(秒):").pack(side=tk.LEFT)
        self.dir_timeout = ttk.Entry(timeout_frame, width=10)
        self.dir_timeout.pack(side=tk.LEFT, padx=5)
        self.dir_timeout.insert(0, "3")

        # 文件后缀设置
        ext_frame = ttk.Frame(settings_frame)
        ext_frame.pack(fill=tk.X, pady=5)
        ttk.Label(ext_frame, text="文件后缀:").pack(side=tk.LEFT)
        self.file_extensions = ttk.Entry(ext_frame, width=30)
        self.file_extensions.pack(side=tk.LEFT, padx=5)
        self.file_extensions.insert(0, "php,asp,aspx,jsp,html,zip,bak")

        # 开始扫描按钮
        ttk.Button(dialog, text="开始扫描", 
                  command=lambda: self.start_dir_scan(dialog)).pack(pady=10)

    def select_dict_file(self):
        """选择字典文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if file_path:
            self.dict_path.set(file_path)

    def get_common_dirs(self):
        """返回常见目录列表"""
        return [
            'admin', 'administrator', 'backup', 'bak', 'beta', 'bin', 'cms',
            'config', 'data', 'db', 'debug', 'demo', 'dev', 'doc', 'docs',
            'download', 'downloads', 'file', 'files', 'home', 'htdocs', 'html',
            'image', 'images', 'img', 'include', 'includes', 'install', 'log',
            'login', 'logs', 'manage', 'management', 'manager', 'manual',
            'new', 'news', 'old', 'panel', 'php', 'phpmyadmin', 'private',
            'root', 'secure', 'security', 'setup', 'site', 'sql', 'staff',
            'staging', 'static', 'stats', 'status', 'storage', 'svn', 'system',
            'temp', 'test', 'testing', 'tmp', 'upload', 'uploads', 'user',
            'users', 'web', 'webadmin', 'webroot', 'www', 'wwwroot'
        ]

    def start_dir_scan(self, dialog):
        """开始目录扫描"""
        urls = self.get_urls()
        if not urls:
            messagebox.showwarning("警告", "请输入至少一个URL")
            return

        try:
            thread_count = int(self.dir_thread_count.get())
            timeout = float(self.dir_timeout.get())
            extensions = [ext.strip() for ext in self.file_extensions.get().split(',')]
            
            if self.dir_scan_mode.get() == "custom":
                if not self.dict_path.get():
                    raise ValueError("请选择字典文件")
                try:
                    with open(self.dict_path.get(), 'r', encoding='utf-8') as f:
                        directories = [line.strip() for line in f if line.strip()]
                except Exception as e:
                    raise ValueError(f"读取字典文件失败: {str(e)}")
            else:
                directories = self.get_common_dirs()
            
            dialog.destroy()
        except ValueError as e:
            messagebox.showerror("错误", str(e))
            return

        # 清空进度显示
        self.progress_text.configure(state='normal')
        self.progress_text.delete('1.0', tk.END)
        self.progress_text.configure(state='disabled')

        def scan_url(url, path):
            """扫描单个URL路径"""
            try:
                full_url = urljoin(url, path)
                response = requests.get(full_url, timeout=timeout, 
                                     allow_redirects=False, verify=False)
                if response.status_code in [200, 301, 302]:
                    return full_url, response.status_code
                return None
            except:
                return None

        def scan_host(url):
            """扫描单个主机"""
            try:
                if not url.startswith(('http://', 'https://')):
                    url = 'http://' + url

                result = f"\n开始扫描网站: {url}\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, result)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')

                # 生成所有要扫描的路径
                paths_to_scan = []
                for directory in directories:
                    paths_to_scan.append(directory + '/')
                    for ext in extensions:
                        if ext:
                            paths_to_scan.append(f"{directory}/index.{ext}")
                            paths_to_scan.append(f"{directory}.{ext}")

                # 使用线程池扫描路径
                found_paths = []
                with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
                    future_to_path = {executor.submit(scan_url, url, path): path 
                                    for path in paths_to_scan}
                    for future in concurrent.futures.as_completed(future_to_path):
                        result = future.result()
                        if result:
                            found_url, status_code = result
                            found_paths.append((found_url, status_code))
                            result = f"[{status_code}] {found_url}\n"
                            self.progress_text.configure(state='normal')
                            self.progress_text.insert(tk.END, result)
                            self.progress_text.see(tk.END)
                            self.progress_text.configure(state='disabled')

                if not found_paths:
                    result = f"未发现可访问的目录\n"
                    self.progress_text.configure(state='normal')
                    self.progress_text.insert(tk.END, result)
                    self.progress_text.see(tk.END)
                    self.progress_text.configure(state='disabled')

                # 保存结果到文件
                parsed_url = urlparse(url)
                result_file = f"dirscan_{parsed_url.netloc}.txt"
                with open(result_file, 'w', encoding='utf-8') as f:
                    f.write(f"目录扫描结果 - {url}\n")
                    f.write("=" * 50 + "\n")
                    for found_url, status_code in found_paths:
                        f.write(f"[{status_code}] {found_url}\n")

                result = f"\n扫描结果已保存到: {result_file}\n" + "-" * 50 + "\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, result)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')

            except Exception as e:
                error_msg = f"扫描 {url} 时出错: {str(e)}\n" + "-" * 50 + "\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, error_msg)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')
                logging.error(f"Directory scan failed for {url}: {str(e)}")

        # 在新线程中运行目录扫描
        threading.Thread(target=lambda: [scan_host(url) for url in urls], 
                       daemon=True).start()

    def show_weak_password_dialog(self):
        """显示弱口令检测对话框"""
        dialog = tk.Toplevel(self.window)
        dialog.title("弱口令检测")
        dialog.geometry("500x450")
        dialog.transient(self.window)
        dialog.grab_set()

        # 创建服务选择框架
        service_frame = ttk.LabelFrame(dialog, text="检测服务", padding="5")
        service_frame.pack(fill=tk.X, padx=5, pady=5)

        # 系统服务
        system_frame = ttk.LabelFrame(service_frame, text="系统服务", padding="5")
        system_frame.pack(fill=tk.X, pady=2)
        
        self.check_ftp = tk.BooleanVar(value=True)
        self.check_ssh = tk.BooleanVar(value=True)
        self.check_telnet = tk.BooleanVar(value=True)
        self.check_smb = tk.BooleanVar(value=True)
        
        ttk.Checkbutton(system_frame, text="FTP (21)", variable=self.check_ftp).pack(side=tk.LEFT)
        ttk.Checkbutton(system_frame, text="SSH (22)", variable=self.check_ssh).pack(side=tk.LEFT)
        ttk.Checkbutton(system_frame, text="Telnet (23)", variable=self.check_telnet).pack(side=tk.LEFT)
        ttk.Checkbutton(system_frame, text="SMB (445)", variable=self.check_smb).pack(side=tk.LEFT)

        # 数据库服务
        db_frame = ttk.LabelFrame(service_frame, text="数据库服务", padding="5")
        db_frame.pack(fill=tk.X, pady=2)
        
        self.check_mssql = tk.BooleanVar(value=True)
        self.check_mysql = tk.BooleanVar(value=True)
        self.check_postgresql = tk.BooleanVar(value=True)
        self.check_mongodb = tk.BooleanVar(value=True)
        
        ttk.Checkbutton(db_frame, text="MSSQL (1433)", variable=self.check_mssql).pack(side=tk.LEFT)
        ttk.Checkbutton(db_frame, text="MySQL (3306)", variable=self.check_mysql).pack(side=tk.LEFT)
        ttk.Checkbutton(db_frame, text="PostgreSQL (5432)", variable=self.check_postgresql).pack(side=tk.LEFT)
        ttk.Checkbutton(db_frame, text="MongoDB (27017)", variable=self.check_mongodb).pack(side=tk.LEFT)

        # 中间件服务
        middleware_frame = ttk.LabelFrame(service_frame, text="中间件服务", padding="5")
        middleware_frame.pack(fill=tk.X, pady=2)
        
        self.check_tomcat = tk.BooleanVar(value=True)
        self.check_weblogic = tk.BooleanVar(value=True)
        self.check_phpmyadmin = tk.BooleanVar(value=True)
        
        ttk.Checkbutton(middleware_frame, text="Tomcat (8080)", variable=self.check_tomcat).pack(side=tk.LEFT)
        ttk.Checkbutton(middleware_frame, text="WebLogic (7001)", variable=self.check_weblogic).pack(side=tk.LEFT)
        ttk.Checkbutton(middleware_frame, text="phpMyAdmin (80)", variable=self.check_phpmyadmin).pack(side=tk.LEFT)

        # 字典设置
        dict_frame = ttk.LabelFrame(dialog, text="字典设置", padding="5")
        dict_frame.pack(fill=tk.X, padx=5, pady=5)

        # 用户名字典
        username_frame = ttk.Frame(dict_frame)
        username_frame.pack(fill=tk.X, pady=2)
        self.username_dict_path = tk.StringVar()
        ttk.Label(username_frame, text="用户名字典:").pack(side=tk.LEFT)
        ttk.Entry(username_frame, textvariable=self.username_dict_path, width=30).pack(side=tk.LEFT, padx=5)
        ttk.Button(username_frame, text="浏览", 
                   command=lambda: self.select_dict_file_for_var(self.username_dict_path)).pack(side=tk.LEFT)

        # 密码字典
        password_frame = ttk.Frame(dict_frame)
        password_frame.pack(fill=tk.X, pady=2)
        self.password_dict_path = tk.StringVar()
        ttk.Label(password_frame, text="密码字典:").pack(side=tk.LEFT)
        ttk.Entry(password_frame, textvariable=self.password_dict_path, width=30).pack(side=tk.LEFT, padx=5)
        ttk.Button(password_frame, text="浏览", 
                   command=lambda: self.select_dict_file_for_var(self.password_dict_path)).pack(side=tk.LEFT)

        # 扫描设置
        settings_frame = ttk.LabelFrame(dialog, text="扫描设置", padding="5")
        settings_frame.pack(fill=tk.X, padx=5, pady=5)

        # 线程数设置
        thread_frame = ttk.Frame(settings_frame)
        thread_frame.pack(fill=tk.X, pady=2)
        ttk.Label(thread_frame, text="线程数:").pack(side=tk.LEFT)
        self.weak_thread_count = ttk.Entry(thread_frame, width=10)
        self.weak_thread_count.pack(side=tk.LEFT, padx=5)
        self.weak_thread_count.insert(0, "10")

        # 超时设置
        timeout_frame = ttk.Frame(settings_frame)
        timeout_frame.pack(fill=tk.X, pady=2)
        ttk.Label(timeout_frame, text="超时时间(秒):").pack(side=tk.LEFT)
        self.weak_timeout = ttk.Entry(timeout_frame, width=10)
        self.weak_timeout.pack(side=tk.LEFT, padx=5)
        self.weak_timeout.insert(0, "3")

        # 开始检测按钮
        ttk.Button(dialog, text="开始检测", 
                   command=lambda: self.start_weak_password_scan(dialog)).pack(pady=10)

    def select_dict_file_for_var(self, var):
        """为指定的变量选择字典文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if file_path:
            var.set(file_path)

    def get_default_usernames(self):
        """返回默认用户名列表"""
        return [
            'admin', 'administrator', 'root', 'test', 'guest', 'user',
            'manager', 'webadmin', 'system', 'sa', 'oracle', 'mysql',
            'postgres', 'tomcat', 'weblogic', 'admin123', 'administrator123'
        ]

    def get_default_passwords(self):
        """返回默认密码列表"""
        return [
            '123456', 'admin', 'admin123', 'root', 'password', '123456789',
            'qwerty', '12345678', '111111', '1234567890', 'abc123', 'password123',
            'P@ssw0rd', 'admin888', 'root123', 'system', 'test123', 'manager',
            '', 'guest', 'default', 'pass123', 'password1'
        ]

    def start_weak_password_scan(self, dialog):
        """开始弱口令扫描"""
        urls = self.get_urls()
        if not urls:
            messagebox.showwarning("警告", "请输入至少一个URL")
            return

        try:
            thread_count = int(self.weak_thread_count.get())
            timeout = float(self.weak_timeout.get())

            # 获取用户名和密码列表
            usernames = self.get_default_usernames()
            passwords = self.get_default_passwords()

            # 如果指定了字典文件，则使用字典文件
            if self.username_dict_path.get():
                try:
                    with open(self.username_dict_path.get(), 'r', encoding='utf-8') as f:
                        usernames = [line.strip() for line in f if line.strip()]
                except Exception as e:
                    raise ValueError(f"读取用户名字典文件失败: {str(e)}")

            if self.password_dict_path.get():
                try:
                    with open(self.password_dict_path.get(), 'r', encoding='utf-8') as f:
                        passwords = [line.strip() for line in f if line.strip()]
                except Exception as e:
                    raise ValueError(f"读取密码字典文件失败: {str(e)}")

            dialog.destroy()
        except ValueError as e:
            messagebox.showerror("错误", str(e))
            return

        # 清空进度显示
        self.progress_text.configure(state='normal')
        self.progress_text.delete('1.0', tk.END)
        self.progress_text.configure(state='disabled')

        def check_service(host, port, service_type, username, password):
            """检查单个服务的弱口令"""
            try:
                result = None
                if service_type == "ftp" and self.check_ftp.get():
                    result = self.check_ftp_password(host, username, password, timeout)
                elif service_type == "ssh" and self.check_ssh.get():
                    result = self.check_ssh_password(host, username, password, timeout)
                elif service_type == "mysql" and self.check_mysql.get():
                    result = self.check_mysql_password(host, username, password, timeout)
                # ... 其他服务的检查方法 ...

                if result:
                    return f"[成功] {service_type}://{host}:{port} - {username}:{password}\n"
            except Exception as e:
                logging.error(f"Check {service_type} password failed for {host}: {str(e)}")
            return None

        def scan_host(url):
            """扫描单个主机的所有服务"""
            try:
                parsed_url = urlparse(url)
                host = parsed_url.hostname or url
                if not host:
                    return

                result = f"\n开始扫描主机: {host}\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, result)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')

                # 定义要扫描的服务
                services = []
                if self.check_ftp.get(): services.append(("ftp", 21))
                if self.check_ssh.get(): services.append(("ssh", 22))
                if self.check_mysql.get(): services.append(("mysql", 3306))
                # ... 添加其他服务 ...

                # 使用线程池扫描服务
                found_weak_passwords = []
                with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
                    futures = []
                    for service_type, port in services:
                        for username in usernames:
                            for password in passwords:
                                futures.append(
                                    executor.submit(check_service, host, port, 
                                                 service_type, username, password)
                                )

                    for future in concurrent.futures.as_completed(futures):
                        result = future.result()
                        if result:
                            found_weak_passwords.append(result)
                            self.progress_text.configure(state='normal')
                            self.progress_text.insert(tk.END, result)
                            self.progress_text.see(tk.END)
                            self.progress_text.configure(state='disabled')

                if not found_weak_passwords:
                    result = f"未发现弱口令\n"
                    self.progress_text.configure(state='normal')
                    self.progress_text.insert(tk.END, result)
                    self.progress_text.see(tk.END)
                    self.progress_text.configure(state='disabled')

                # 保存结果到文件
                result_file = f"weakpass_{host}.txt"
                with open(result_file, 'w', encoding='utf-8') as f:
                    f.write(f"弱口令检测结果 - {host}\n")
                    f.write("=" * 50 + "\n")
                    for result in found_weak_passwords:
                        f.write(result)

                result = f"\n扫描结果已保存到: {result_file}\n" + "-" * 50 + "\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, result)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')

            except Exception as e:
                error_msg = f"扫描 {url} 时出错: {str(e)}\n" + "-" * 50 + "\n"
                self.progress_text.configure(state='normal')
                self.progress_text.insert(tk.END, error_msg)
                self.progress_text.see(tk.END)
                self.progress_text.configure(state='disabled')
                logging.error(f"Weak password scan failed for {url}: {str(e)}")

        # 在新线程中运行弱口令扫描
        threading.Thread(target=lambda: [scan_host(url) for url in urls], 
                       daemon=True).start()

    def check_ftp_password(self, host, username, password, timeout):
        """检查FTP密码"""
        try:
            ftp = ftplib.FTP(timeout=timeout)
            ftp.connect(host, 21)
            ftp.login(username, password)
            ftp.quit()
            return True
        except:
            return False

    def check_ssh_password(self, host, username, password, timeout):
        """检查SSH密码"""
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(host, 22, username, password, timeout=timeout)
            ssh.close()
            return True
        except:
            return False

    def check_mysql_password(self, host, username, password, timeout):
        """检查MySQL密码"""
        try:
            conn = pymysql.connect(
                host=host,
                user=username,
                password=password,
                connect_timeout=timeout
            )
            conn.close()
            return True
        except:
            return False

NUM = 5
if __name__ == '__main__':
    # 启动GUI
    gui = DetectionGUI()
    gui.run()
